﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Mobwiz.SpellChecker.Core
{
    public class SpellChecker : Mobwiz.SpellChecker.Core.ISpellChecker
    {
        #region Properties

        /// <summary>
        /// Phonet used to transform the word
        /// </summary>
        public IPhonet Phonet { get; set; }

        /// <summary>
        /// The dictionary used to check the spell.
        /// </summary>
        public ISpellDictionary SpellDictionary { get; set; }

        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="phonet"></param>
        /// <param name="dictionary"></param>
        public SpellChecker(IPhonet phonet, ISpellDictionary dictionary)
        {
            this.Phonet = phonet;
            this.SpellDictionary = dictionary;
        }

        /// <summary>
        /// Check if the word is correct spelled.
        /// </summary>
        /// <param name="word"></param>
        /// <returns></returns>
        public bool CheckWord(string word)
        {
            if (string.IsNullOrEmpty(word))
            {
                return true;
            }

            string phonetCode = this.Phonet.Transform(word);

            var wordList = this.SpellDictionary.GetWordsByCode(phonetCode);
            foreach (var w in wordList)
            {
                if (word.Equals(w, StringComparison.CurrentCultureIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Get the suggested word for the misspelled word
        /// </summary>
        /// <param name="originalWord"></param>
        /// <returns></returns>
        public IList<SuggestedWord> GetSuggestWords(string originalWord)
        {
            IList<string> nearMissCodes = GetNearMissCodes(originalWord);

            IList<SuggestedWord> sugWords = new List<SuggestedWord>();

            int distance = 0;

            foreach (string code in nearMissCodes)
            {
                IEnumerable<string> words = this.SpellDictionary.GetWordsByCode(code);
                foreach (string word in words)
                {
                    if ((distance = EditDistance.GetDistance(originalWord, word)) <= ConfigurationEditDistance.SUG_EDIT_DISTANCE_THRESHOLD)
                    {
                        if (sugWords.Count(p => p.Word == word) > 0)
                        {
                            continue;
                        }

                        sugWords.Add(new SuggestedWord(word, distance));
                    }
                }
            }

            if (sugWords.Count > 0)
            {
                return sugWords;
            }

            int bestScore = int.MaxValue;
            distance = 0;

            List<SuggestedWord> guessWords = new List<SuggestedWord>();

            IEnumerable<string> wordsFound = this.SpellDictionary.GetWordsByCode(this.Phonet.Transform(originalWord));

            foreach (string word in wordsFound)
            {
                if ((distance = EditDistance.GetDistance(originalWord, word)) <= bestScore)
                {
                    guessWords.Add(new SuggestedWord(word, distance));
                    bestScore = distance;
                }
            }

            foreach (SuggestedWord sugWord in guessWords)
            {
                if (sugWords.Count(p => p.Word == sugWord.Word) > 0)
                {
                    continue;
                }               

                if (sugWord.EditDistance <= bestScore)
                {
                    //yield return sugWord;
                    sugWords.Add(sugWord);
                }
            }

            return sugWords.OrderBy(word => word.EditDistance).ToList();
        }

        /// <summary>
        /// Add the word to the dictionary
        /// </summary>
        /// <param name="word"></param>
        public void AddWord(string word)
        {
            if (string.IsNullOrEmpty(word))
            {
                return;
            }

            string code = this.Phonet.Transform(word);

            this.SpellDictionary.AddWord(code, word);
        }

        #region Private functions

        private IList<string> GetNearMissCodes(string incomingWord)
        {
            //throw new NotImplementedException();
            string code = "";
            string word = incomingWord;

            IList<string> nearMissCodes = new List<string>();

            code = this.Phonet.Transform(incomingWord);
            //yield return code;
            nearMissCodes.Add(code);

            char[] charArray = incomingWord.ToCharArray();
            char a;
            char b;

            int i, j;
            //swap the two chars internal
            //  just like: wrod  -- 
            //   first:   rwod -> wrod  --> word  -> wrod -> wrdo ..END
            // in this loop, get every changed word's Code and put them into the nearmisscodes Hash table
            // just like nearmisscodes.put(s,s);

            for (i = 0; i < word.Length - 1; i++)
            {
                a = charArray[i];
                b = charArray[i + 1];
                charArray[i] = b;
                charArray[i + 1] = a;
                //for debug *****************************************
                TraceInfo("NearMiss SWAP", new string(charArray));
                /// Debug info *************************************
                String s = this.Phonet.Transform(new String(charArray));
                nearMissCodes.Add(s);
                charArray[i] = a;
                charArray[i + 1] = b;
            }

            // do phonetic replace 
            // first get the replace list.
            // then change the char in the original word with the char in the replacelist one by one
            // and get the code of the every changed word, and put them into the nearmisscodes hashtable
            char[] replacelist = this.Phonet.GetReplaceList();

            //change
            charArray = word.ToCharArray();
            char original;
            for (i = 0; i < word.Length; i++)
            {
                original = charArray[i];
                for (j = 0; j < replacelist.Count(); j++)
                {
                    charArray[i] = replacelist[j];
                    //for debug *****************************************
                    TraceInfo("NearMiss REPLACE", new string(charArray));
                    /// Debug info *************************************
                    String s = this.Phonet.Transform(new String(charArray));
                    nearMissCodes.Add(s);
                }
                charArray[i] = original;
            }

            //add
            // add every char in the replacelist to the word from the end of the word to the beginning 
            // 从后至前的将replacelist中的字母加入到单词中，并将其语音码放入nearmisscodes Hashtable中。
            // pay attention: insert only one character.
            //
            //

            charArray = (word += " ").ToCharArray(); // add a blank to the end of the word
            int iy = charArray.Count() - 1; // the word's true length
            while (true)
            {
                for (j = 0; j < replacelist.Count(); j++)
                {
                    charArray[iy] = replacelist[j];
                    //for debug *****************************************
                    TraceInfo("NearMiss INSERT", new string(charArray));
                    /// Debug info *************************************
                    String s = this.Phonet.Transform(new String(charArray));
                    nearMissCodes.Add(s);
                }
                if (iy == 0)
                    break;
                charArray[iy] = charArray[iy - 1];
                --iy;
            }

            //delete , try to delete each character in the word 
            // Just one word!
            //
            word = word.Trim();
            charArray = word.ToCharArray();
            char[] charArray2 = new char[charArray.Count() - 1];
            for (int ix = 0; ix < charArray2.Count(); ix++)
            {
                charArray2[ix] = charArray[ix];
            }

            a = charArray[charArray.Count() - 1];
            int ii = charArray2.Count();
            while (true)
            {
                //for debug *****************************************
                TraceInfo("NearMiss DELETE", new string(charArray2));
                /// Debug info *************************************
                String s = this.Phonet.Transform(new String(charArray2)); //Bug fixed bye dove. is this a bug????? sure!!this is the bug!
                nearMissCodes.Add(s);
                if (ii == 0)
                    break;
                b = a;
                a = charArray2[ii - 1];
                charArray2[ii - 1] = b;
                --ii;
            }

            return nearMissCodes;

        }

        private IEnumerable<SuggestedWord> GetWordsByCodes(string incomingWord, IList<string> codes)
        {
            int distance = 0;

            foreach (string code in codes)
            {
                IEnumerable<string> words = SpellDictionary.GetWordsByCode(code);
                foreach (string word in words)
                {
                    if ((distance = EditDistance.GetDistance(incomingWord, word)) <= ConfigurationEditDistance.SUG_EDIT_DISTANCE_THRESHOLD)
                        yield return new SuggestedWord(word, distance);
                }
            }
        }

        [Conditional("TRACE_ON")]
        private void TraceInfo(string title, string info)
        {
            Console.WriteLine(title + ":" + info);
        }

        #endregion
    }
}
